module("framework.core.taskdispatcher.TaskDispatcher", package.seeall)
--[[
    定时调度器
    基于Unity的Update()驱动
    同时只能存在一个任务[cb+cbobj]在任务队列中
    如果要加入新的[cb+cbobj]任务，则必须先调用cancelTask取消原有的任务
]]
---@class TaskDispatcher
local TaskDispatcher = {}

--任务状态定义
TaskDispatcher.Idle = 1
TaskDispatcher.Active = 2
TaskDispatcher.ToInsert = 3
TaskDispatcher.ToDelete = 4

function TaskDispatcher.init()
    UpdateBeat:Add(TaskDispatcher._onUnityUpdate, TaskDispatcher)
    TaskDispatcher._taskPool = TaskItem.createPool()
    --任务队列
    TaskDispatcher._allTasks = {}
    TaskDispatcher._onceIdxList = {}
    --修改队列
    TaskDispatcher._deltaTasks = {}
    TaskDispatcher._isDispatching = false
end

---delay秒之后执行某个函数
---@param callback fun(obj:any) 回调函数
---@param cbObj any 回调函数的所属对象，静态函数传 nil
---@param delay number 延迟时间，单位 s
function TaskDispatcher.runDelay(callback, cbObj, delay)
    if not callback or not delay then
        logError("TaskDispatcher.runDelay, callback or delay should not be null!")
        return
    end
    TaskDispatcher._addNewTask(callback, cbObj, delay, 1, false)
end

---interval秒之后执行某个函数，然后间隔interval秒重复执行
---@param callback fun(obj:any) 回调函数
---@param cbObj any 回调函数的所属对象，静态函数传 nil
---@param interval number 延迟时间/循环执行间隔时间，单位 s
---@param repeatCount number 重复次数，小于等于0或者不传，则为无限次
function TaskDispatcher.runRepeat(callback, cbObj, interval, repeatCount)
    if not callback or not interval then
        logError("TaskDispatcher.runDelay, callback or interval should not be null!")
        return
    end
    TaskDispatcher._addNewTask(callback, cbObj, interval, repeatCount or -1, true)
end

---取消某个任务调度
---@param callback fun(obj:any) 回调函数
---@param cbObj any 回调函数的所属对象，静态函数传 nil
function TaskDispatcher.cancelTask(callback, cbObj)
    --如果是在回调任务执行时
    if TaskDispatcher._isDispatching then
        --在任务队列中的标记为删除，延后删除
        local alreadyTask = TaskDispatcher._getTaskWhichCbInDispatch(callback, cbObj)
        if alreadyTask then
            alreadyTask.status = TaskDispatcher.ToDelete
        end
        --在修改列表中的直接删除
        TaskDispatcher._removeFromDeltaQueue(callback, cbObj)
        local deleteTask = TaskDispatcher._taskPool:getObject()
        deleteTask.status = TaskDispatcher.ToDelete
        deleteTask:setCb(callback, cbObj)
        table.insert(TaskDispatcher._deltaTasks, deleteTask)
    else
        --不在回调时执行，则直接从任务队列中删除
        TaskDispatcher._directDelete(callback, cbObj)
    end
end

---添加一个任务
---@param callback fun(obj:any)
---@param cbObj any
---@param interval number
---@param repeatCount number
---@param isLoop boolean
function TaskDispatcher._addNewTask(callback, cbObj, interval, repeatCount, isLoop)
    local toDelete = false
    if TaskDispatcher._isDispatching then
        local deltaTask = TaskDispatcher._getTaskInDeltaQueue(callback, cbObj)
        if deltaTask then
            if TaskDispatcher.ToInsert == deltaTask.status then
                return
            elseif TaskDispatcher.ToDelete == deltaTask.status then
                toDelete = true
            end
        end
    end

    local alreadyTask = TaskDispatcher._getTaskWhichCbInDispatch(callback, cbObj)
    --循环任务不能在任务本体中再次添加自己，延迟任务可以在其任务本体中添加本体的延迟or循环调用
    if nil ~= alreadyTask and alreadyTask.isLoop and alreadyTask.status ~= TaskDispatcher.ToDelete and not toDelete then
        return
    end

    if TaskDispatcher._isDispatching then
        TaskDispatcher._removeFromDeltaQueue(callback, cbObj)
    end

    local newTask = TaskDispatcher._taskPool:getObject()
    newTask:setCb(callback, cbObj)
    newTask.interval = interval
    newTask.isLoop = isLoop
    newTask.repeatCount = repeatCount
    newTask.addFrame = TaskItem.frameCount
    newTask.status = TaskDispatcher.Active

    if TaskDispatcher._isDispatching then
        newTask.status = TaskDispatcher.ToInsert
        table.insert(TaskDispatcher._deltaTasks, newTask)
    else
        TaskDispatcher._directAdd(newTask)
    end
end

function TaskDispatcher._onUnityUpdate()
    TaskDispatcher._isDispatching = true
    TaskDispatcher._taskTick(Time.deltaTime)
    TaskDispatcher._isDispatching = false
    TaskDispatcher._doAddOrRemove()
    TaskItem.frameCount = TaskItem.frameCount + 1
end

---执行删除或者添加
function TaskDispatcher._doAddOrRemove()
    local count = #TaskDispatcher._deltaTasks
    local tmpTask = nil
    for idx = 1, count do
        tmpTask = TaskDispatcher._deltaTasks[idx]
        if TaskDispatcher.ToDelete == tmpTask.status then
            TaskDispatcher._directDelete(tmpTask.callback, tmpTask.cbObj)
        elseif TaskDispatcher.ToInsert == tmpTask.status then
            TaskDispatcher._directAdd(tmpTask)
        end
        TaskDispatcher._deltaTasks[idx] = nil
    end
end

---从延迟修改表中删除某个回调, 只删除一次
---@param callback fun(obj:any)
---@param cbObj any
function TaskDispatcher._removeFromDeltaQueue(callback, cbObj)
    local count = #TaskDispatcher._deltaTasks
    local tmpTask = nil
    for idx = count, 1, -1 do
        tmpTask = TaskDispatcher._deltaTasks[idx]
        if tmpTask.callback == callback and tmpTask.cbObj == cbObj then
            TaskDispatcher._taskPool:putObject(tmpTask)
            table.remove(TaskDispatcher._deltaTasks, idx)
            break
        end
    end
end

--直接添加新的任务
--如果任务队列中[存在标记删除的相同任务]或者[不存在相同任务]，则加进去；否则加不进去，需要先调用cancelTask
function TaskDispatcher._directAdd(taskItem)
    local alreadyTask, index = TaskDispatcher._getTaskWhichCbInDispatch(taskItem.callback, taskItem.cbObj)
    if alreadyTask and alreadyTask.status ~= TaskDispatcher.ToDelete then
        TaskDispatcher._taskPool:putObject(taskItem)
        return
    end
    --先删除，再添加
    if alreadyTask then
        table.remove(TaskDispatcher._allTasks, index)
    end
    taskItem.status = TaskDispatcher.Active
    table.insert(TaskDispatcher._allTasks, taskItem)
end

--直接删除新的任务
function TaskDispatcher._directDelete(callback, cbObj)
    local count = #TaskDispatcher._allTasks
    local tmpTask = nil
    for idx = count, 1, -1 do
        tmpTask = TaskDispatcher._allTasks[idx]
        if tmpTask.callback == callback and tmpTask.cbObj == cbObj then
            TaskDispatcher._taskPool:putObject(tmpTask)
            table.remove(TaskDispatcher._allTasks, idx)
        end
    end
end

--查找某个回调是否已经在任务队列中了
function TaskDispatcher._getTaskWhichCbInDispatch(callback, cbObj)
    local count = #TaskDispatcher._allTasks
    local tmpTask = nil
    for idx = count, 1, -1 do
        tmpTask = TaskDispatcher._allTasks[idx]
        if tmpTask.callback == callback and tmpTask.cbObj == cbObj then
            return tmpTask, idx
        end
    end
    return nil, 0
end

--在修改队列中查找某个回调任务
function TaskDispatcher._getTaskInDeltaQueue(callback, cbObj)
    local tmpTask = nil
    local count = #TaskDispatcher._deltaTasks
    for idx = count, 1, -1 do
        tmpTask = TaskDispatcher._deltaTasks[idx]
        if tmpTask.callback == callback and tmpTask.cbObj == cbObj then
            return tmpTask
        end
    end
    return nil
end

--遍历所有的任务，不是要删除的任务，判断时间是否到，到则执行回调
--非循环任务如果执行了，则直接删除
function TaskDispatcher._taskTick(deltaTime)
    local count = #TaskDispatcher._allTasks
    local hasInvoked = false
    local tmpTask = nil
    for idx = 1, count do
        tmpTask = TaskDispatcher._allTasks[idx]
        if TaskDispatcher.ToDelete ~= tmpTask.status then
            hasInvoked = tmpTask:update(deltaTime)
            if hasInvoked and tmpTask.repeatCount == 0 then
                table.insert(TaskDispatcher._onceIdxList, idx)
            end
        end
    end

    count = #TaskDispatcher._onceIdxList
    for idx = count, 1, -1 do
        local removeIdx = TaskDispatcher._onceIdxList[idx]
        TaskDispatcher._taskPool:putObject(TaskDispatcher._allTasks[removeIdx])
        table.remove(TaskDispatcher._allTasks, removeIdx)
        table.remove(TaskDispatcher._onceIdxList, idx)
    end
end

function TaskDispatcher._logToBeDeleteItems()
    local log = "TaskDispatcher._logToBeDeleteItems: "
    for index, value in ipairs(TaskDispatcher._allTasks) do
        if value.status == TaskDispatcher.ToDelete then
            log = log .. index .. " = " .. value:logStr() .. "\n"
        end
    end
    logWarn(log)
end

function TaskDispatcher._logAllTasks()
    local log = "TaskDispatcher._logToBeDeleteItems: "
    for index, value in ipairs(TaskDispatcher._allTasks) do
        log = log .. index .. " = " .. value:logStr() .. "\n"
    end
    logWarn(log)
end

return TaskDispatcher
